//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#include "particletemplate.h"
#include "emittertemplate.h"
#include <common/range.h>
#include <common/restorestream.h>
#include <common/restoreerror.h>
//
namespace model
{
//ParticleTemplate::TimeMark
ParticleTemplate::TimeMark::TimeMark()
:
   mode(from_start),time(0)
{
}
ParticleTemplate::TimeMark::TimeMark(const ParticleTemplate::TimeMark &p_other)
:
   mode(p_other.mode),time(p_other.time),other(p_other.other)
{
}
ParticleTemplate::TimeMark &ParticleTemplate::TimeMark::operator=(const ParticleTemplate::TimeMark &p_other)
{
   if(&p_other!=this)
   {
      mode=p_other.mode;
      time=p_other.time;
      other=p_other.other;
   }
   return *this;
}
//ParticleTemplate::EmitterBlock
ParticleTemplate::EmitterBlock::EmitterBlock()
:
   interval_min(0),interval_max(0)
{
}
ParticleTemplate::EmitterBlock::EmitterBlock(const ParticleTemplate::EmitterBlock &p_other)
:
   interval_min(p_other.interval_min),interval_max(p_other.interval_max),
   time1(p_other.time1),time2(p_other.time2),
   emitter(p_other.emitter)
{
}
ParticleTemplate::EmitterBlock &ParticleTemplate::EmitterBlock::operator=(const ParticleTemplate::EmitterBlock &p_other)
{
   if(&p_other!=this)
   {
      interval_min=p_other.interval_min;
      interval_max=p_other.interval_max;
      time1=p_other.time1;
      time2=p_other.time2;
      emitter=p_other.emitter;
   }
   return *this;
}
//ParticleTemplate::ColorControlPoint
ParticleTemplate::ColorControlPoint::ColorControlPoint()
:
color(),time()
{
}
ParticleTemplate::ColorControlPoint::ColorControlPoint(const ParticleTemplate::ColorControlPoint &p_other)
:
color(p_other.color),time(p_other.time)
{
}
ParticleTemplate::ColorControlPoint &ParticleTemplate::ColorControlPoint::operator=( const ParticleTemplate::ColorControlPoint &p_other)
{
   if(&p_other!=this)
   {
      color=p_other.color;
      time=p_other.time;
   }
   return *this;
}
//ParticleTemplate::FloatControlPoint
ParticleTemplate::FloatControlPoint::FloatControlPoint()
:
value(),time()
{
}
ParticleTemplate::FloatControlPoint::FloatControlPoint(const ParticleTemplate::FloatControlPoint &p_other)
:
value(p_other.value),time(p_other.time)
{
}
ParticleTemplate::FloatControlPoint &ParticleTemplate::FloatControlPoint::operator=( const ParticleTemplate::FloatControlPoint &p_other)
{
   if(&p_other!=this)
   {
      value=p_other.value;
      time=p_other.time;
   }
   return *this;
}
//ParticleTemplate
ParticleTemplate::ParticleTemplate(QObject *p_parent)
:
   Identified(p_parent),
   visible(true),
   life_time(new RandomDouble::Constant(0)),
   density(1),
   gravitation_enabled(true),
   friction_enabled(true),
   shape(shape_point),
   tile_s(1),
   tile_t(1),
   num_tiles(1),
   tile_speed_mode(tile_speed_relative)
{
}
ParticleTemplate::~ParticleTemplate()
{
}
QString ParticleTemplate::get_name()const
{
   return name;
}
void ParticleTemplate::set_name(QString p_name)
{
   name=p_name;
}
bool ParticleTemplate::get_visible()const
{
   return visible;
}
void ParticleTemplate::set_visible(bool p_visible)
{
   visible=p_visible;
}
RandomDouble ParticleTemplate::get_life_time()const
{
   return life_time;
}
void ParticleTemplate::set_life_time(const RandomDouble &p_life_time)
{
   life_time=p_life_time;
}
float ParticleTemplate::get_density()const
{
   return density;
}
void ParticleTemplate::set_density(float p_density)
{
   density=p_density;
}
bool ParticleTemplate::get_gravitation_enabled()const
{
   return gravitation_enabled;
}
void ParticleTemplate::set_gravitation_enabled(bool p_gravitation_enabled)
{
   gravitation_enabled=p_gravitation_enabled;
}
bool ParticleTemplate::get_friction_enabled()const
{
   return friction_enabled;
}
void ParticleTemplate::set_friction_enabled(bool p_friction_enabled)
{
   friction_enabled=p_friction_enabled;
}
ParticleTemplate::Shape ParticleTemplate::get_shape()const
{
   return shape;
}
void ParticleTemplate::set_shape(Shape p_shape)
{
   shape=p_shape;
}
QString ParticleTemplate::get_texture_file()const
{
   return texture_file;
}
void ParticleTemplate::set_texture_file(QString p_texture_file)
{
   texture_file=p_texture_file;
}
unsigned int ParticleTemplate::get_tile_s()const
{
   return tile_s;
}
unsigned int ParticleTemplate::get_tile_t()const
{
   return tile_t;
}
void ParticleTemplate::set_tile_s(unsigned int p_tile_s)
{
   tile_s=p_tile_s;
}
void ParticleTemplate::set_tile_t( unsigned int p_tile_t)
{
   tile_t=p_tile_t;
}
void ParticleTemplate::set_tile(unsigned int p_tile_s,unsigned int p_tile_t)
{
   tile_s=p_tile_s;
   tile_t=p_tile_t;
}
float ParticleTemplate::get_num_tiles()const
{
   return num_tiles;
}
void ParticleTemplate::set_num_tiles(float p_num_tiles)
{
   num_tiles=p_num_tiles;
}
ParticleTemplate::TileSpeedMode ParticleTemplate::get_tile_speed_mode()const
{
   return tile_speed_mode;
}
void ParticleTemplate::set_tile_speed_mode(TileSpeedMode p_tile_speed_mode)
{
   tile_speed_mode=p_tile_speed_mode;
}
QString ParticleTemplate::compute_time_mark_ancestor(const QString &p_time_mark_name)const
{
   if(!time_marks.contains(p_time_mark_name))
      return p_time_mark_name;
   QString name=p_time_mark_name;
   while(time_marks[name].mode==TimeMark::from_other)
   {
      name=time_marks[name].other;
      if(name==p_time_mark_name)
         return "";
   }
   return name;
}
bool ParticleTemplate::compute_rel_range(QVector<QVector<Range> > &p_rel_ranges,QVector<QVector<char> > &p_rel_state,const QStringList &p_tm,unsigned int p_from,unsigned int p_to)const
{
   if(p_from>p_to)
   {
      std::swap(p_from,p_to);
   }
   if(p_rel_state[p_from][p_to]==101)
      return false;
   if(p_rel_state[p_from][p_to]==102)
      return true;
   //
   p_rel_state[p_from][p_to]=101;
   if(p_from==p_to)
   {
      p_rel_ranges[p_from][p_to]=Range(0.0);
   }
   else
   {
      if(p_from==0)
      {
         if(p_to==1)
         {
            p_rel_ranges[p_from][p_to]=life_time.data().get_range();
         }
         else
         {
            const TimeMark &tm=time_marks[p_tm[p_to-2]];
            switch(tm.mode)
            {
            case TimeMark::from_start:
               p_rel_ranges[p_from][p_to]=tm.time.data().get_range();
               break;
            case TimeMark::from_end:
               if(!compute_rel_range(p_rel_ranges,p_rel_state,p_tm,0,1))
               {
                  return false;
               }
               p_rel_ranges[p_from][p_to]=p_rel_ranges[0][1]+tm.time.data().get_range()*(-1.0);
               break;
            case TimeMark::from_other:
               {
                  int i=2+p_tm.indexOf(tm.other);
                  if(i<2)
                  {
                     return false;
                  }
                  if(!compute_rel_range(p_rel_ranges,p_rel_state,p_tm,0,i))
                  {
                     return false;
                  }
                  p_rel_ranges[p_from][p_to]=p_rel_ranges[0][i]+tm.time.data().get_range();
               }
               break;
            }
         }
      }
      else if(time_marks[p_tm[p_to-2]].mode==TimeMark::from_other)
      {
         const TimeMark &tm=time_marks[p_tm[p_to-2]];
         int i=2+p_tm.indexOf(tm.other);
         if(i<2)
         {
            return false;
         }
         if(!compute_rel_range(p_rel_ranges,p_rel_state,p_tm,p_from,i))
         {
            return false;
         }
         p_rel_ranges[p_from][p_to]=p_rel_ranges[p_from][i]+tm.time.data().get_range();
      }
      else if(p_from>=2&&time_marks[p_tm[p_from-2]].mode==TimeMark::from_other)
      {
         const TimeMark &tm=time_marks[p_tm[p_from-2]];
         int i=2+p_tm.indexOf(tm.other);
         if(i<2)
         {
            return false;
         }
         if(!compute_rel_range(p_rel_ranges,p_rel_state,p_tm,i,p_to))
         {
            return false;
         }
         p_rel_ranges[p_from][p_to]=tm.time.data().get_range()*(-1.0)+p_rel_ranges[i][p_to];
      }
      else
      {
         if(!compute_rel_range(p_rel_ranges,p_rel_state,p_tm,0,p_from))
         {
            return false;
         }
         if(!compute_rel_range(p_rel_ranges,p_rel_state,p_tm,0,p_to))
         {
            return false;
         }
         p_rel_ranges[p_from][p_to]=p_rel_ranges[0][p_from]*(-1.0)+p_rel_ranges[0][p_to];
      }
      p_rel_ranges[p_to][p_from]=p_rel_ranges[p_from][p_to]*(-1.0);
   }
   p_rel_state[p_from][p_to]=102;
   return true;
}
void ParticleTemplate::compute_rel_state(const QVector<QVector<Range> > &p_rel_ranges,QVector<QVector<char> > &p_rel_state,unsigned int p_i,unsigned int p_j)const
{
   if(p_rel_ranges[p_i][p_j].is_null())
   {
      /*Range ri=p_rel_ranges[0][p_i];
      Range rj=p_rel_ranges[0][p_j];
      p_rel_state[p_i][p_j]=ri.is_before(rj);//i j ?*/
      p_rel_state[p_i][p_j]=0;
      qDebug()<<"!!!"<<p_i<<p_j;
   }
   else
   {
      p_rel_state[p_i][p_j]=p_rel_ranges[p_i][p_j].is_before(Range(0.0));
   }
}
struct SortHelper
{
   static QVector<QVector<char> > *rel_state;
   static int max;
   static bool less(int i,int j)
   {
      Q_ASSERT(i>=0&&j>=0&&i<max&&j<max);
      return (*rel_state)[j][i]==-1;
   }
};
QVector<QVector<char> > *SortHelper::rel_state=0;
int SortHelper::max=0;
QStringList ParticleTemplate::compute_time_mark_time_sort(QString &p_error,QStringList &p_bad_tm)const
{
   /* This should calculate a sequence of time marks in the order where the time marks follow each other in time.
    * If not possible the "conflicting" time marks or other error is returned.
    * Return value is the sorted list, empty if error, then p_error and p_bad_tm is filled.
    * p_error is an error text, p_bad_tm contains conflicting time marks if there is problem with time marks
    * (interleaving in time or reaching before start or end).
    *
    * Time marks are coded with an int value, 0 for start (0.0), 1 for end (lifetime), >=2 for time marks in array 'tm' at index "i"-2.
    * 'rel_ranges' stores a relative range of time, rel_ranges[from][to] for smallest and greatest possible time offset between time mark "from" and "to" (interval to get to time "to" when adding it to "from").
    * rel_ranges[0][i] is the absolute min and max time for time mark "i", for i=1 the lifetime.
    * 'rel_state' stores the state of relation, rel_state[from][to] for time mark "from" and "to".
    * It is -1 if tm "to" is for sure before tm "from", 1 if after, and 0 if not comparable or may be before or after.
    * These matrices should be "antisymmetric" (x[i][j]==-x[j][i]) (for ranges the - means the range multiplied by -1).
    * First rel_state contains other codes: 100=initial value and rel_ranges[i][j] not calculated, 101=rel_ranges[i][j] under calculation,102=calculated.
    * After every rel_state is calculated 0 values are filtered out (except for i==j), these are at "wrong" time marks.
    * Then it should be possible to sort time marks by using rel_state==-1 as "lessThan" and so get the sorted sequence.
    */
   QStringList res;
   QStringList const tm=time_marks.keys();
   unsigned int const num_marks=tm.size()+2;
   QVector<QVector<Range> > rel_ranges(num_marks,QVector<Range>(num_marks));
   QVector<QVector<char> > rel_state(num_marks,QVector<char>(num_marks,100));
   for(unsigned int i=0;i<num_marks;++i)
   {
      for(unsigned int j=i;j<num_marks;++j)
      {
         if(!compute_rel_range(rel_ranges,rel_state,tm,i,j))
         {
            qDebug()<<i<<j<<"\n";
            p_error=tr("Internal error","ParticleTemplate");
            return res;
         }
      }
   }
   QSet<unsigned int> bad_tm;
   for(unsigned int i=0;i<num_marks;++i)
   {
      for(unsigned int j=0;j<num_marks;++j)
      {
         compute_rel_state(rel_ranges,rel_state,i,j);
//         qDebug()<<"rel_state["<<i<<","<<j<<"] == "<<static_cast<int>(rel_state[i][j])<<"\n";
         if(i!=j&&rel_state[i][j]==0)
         {
            if(i>=2)
            {
               bad_tm.insert(i);
            }
            if(j>=2)
            {
               bad_tm.insert(j);
            }
         }
      }
   }
   if(rel_ranges[0][1].get_min()<0)
   {
      p_error=tr("Bad lifetime value, may be less than or equal to zero.","ParticleTemplate");
      return res;
   }
   if(!bad_tm.empty())
   {
      p_error=tr("Possible interleaving time mark values.","ParticleTemplate");
      for(QSet<unsigned int>::const_iterator i=bad_tm.begin();i!=bad_tm.end();++i)
      {
         p_bad_tm.append(tm[*i-2]);
      }
      return res;
   }
   //
   QList<int> tm_sort;
   for(unsigned int i=0;i<num_marks;++i)
   {
      tm_sort.append(i);
   }
   SortHelper::rel_state=&rel_state;
   SortHelper::max=num_marks;
   qSort(tm_sort.begin(),tm_sort.end(),SortHelper::less);
   while(!tm_sort.isEmpty()&&tm_sort.front()!=0)
   {
      bad_tm.insert(tm_sort.front());
      tm_sort.pop_front();
   }
   while(!tm_sort.isEmpty()&&tm_sort.back()!=1)
   {
      bad_tm.insert(tm_sort.back());
      tm_sort.pop_back();
   }
   if(!bad_tm.empty())
   {
      p_error=tr("Time marks before particle start or after particle end.","ParticleTemplate");
      for(QSet<unsigned int>::const_iterator i=bad_tm.begin();i!=bad_tm.end();++i)
      {
         p_bad_tm.append(tm[*i-2]);
      }
      return res;
   }
   for(int i=1;i<tm_sort.size()-1;++i)
   {
      res.append(tm[tm_sort[i]-2]);
   }
   p_error.clear();
   return res;
}
void ParticleTemplate::store_data(QDataStream &p_stream)const
{
   p_stream<<name;
   p_stream<<visible;
   p_stream<<life_time;
   p_stream<<density;
   p_stream<<gravitation_enabled;
   p_stream<<friction_enabled;
   p_stream<<static_cast<quint8>(shape);
   p_stream<<texture_file;
   p_stream<<tile_s;
   p_stream<<tile_t;
   p_stream<<num_tiles;
   p_stream<<static_cast<quint8>(tile_speed_mode);
   p_stream<<time_marks;
   p_stream<<emitters;
   p_stream<<color_control_points;
   p_stream<<alpha_control_points;
   p_stream<<size_control_points;
   p_stream<<radius_control_points;
}
void ParticleTemplate::restore_data(RestoreStream &p_stream)
{
   p_stream>>name;
   p_stream>>visible;
   p_stream>>life_time;
   p_stream>>density;
   p_stream>>gravitation_enabled;
   p_stream>>friction_enabled;
   quint8 tmp;
   p_stream>>tmp;
   switch(tmp)
   {
   case shape_point:
      shape=shape_point;
      break;
   case shape_quad:
      shape=shape_quad;
      break;
   case shape_quad_trianglestrip:
      shape=shape_quad_trianglestrip;
      break;
   case shape_hexagon:
      shape=shape_hexagon;
      break;
   default:
      throw(RestoreError("wrong Shape value at model::ParticleTemplate::restore_data"));
   }
   p_stream>>texture_file;
   p_stream>>tile_s;
   p_stream>>tile_t;
   p_stream>>num_tiles;
   p_stream>>tmp;
   switch(tmp)
   {
   case tile_speed_relative:
      tile_speed_mode=tile_speed_relative;
      break;
   case tile_speed_absolute:
      tile_speed_mode=tile_speed_absolute;
      break;
   default:
      throw(RestoreError("wrong TileSpeedMode value at model::ParticleTemplate::restore_data"));
   }
   p_stream>>time_marks;
   p_stream>>emitters;
   p_stream>>color_control_points;
   p_stream>>alpha_control_points;
   p_stream>>size_control_points;
   p_stream>>radius_control_points;
}
}
//
QDataStream &operator<<(QDataStream &p_stream,const model::ParticleTemplate::TimeMark &p_tm)
{
   p_stream<<static_cast<quint8>(p_tm.mode);
   p_stream<<p_tm.time;
   p_stream<<p_tm.other;
   return p_stream;
}
RestoreStream &operator>>(RestoreStream &p_stream,model::ParticleTemplate::TimeMark &p_tm)
{
   quint8 tmp;
   p_stream>>tmp;
   switch(tmp)
   {
   case model::ParticleTemplate::TimeMark::from_start:
      p_tm.mode=model::ParticleTemplate::TimeMark::from_start;
      break;
   case model::ParticleTemplate::TimeMark::from_end:
      p_tm.mode=model::ParticleTemplate::TimeMark::from_end;
      break;
   case model::ParticleTemplate::TimeMark::from_other:
      p_tm.mode=model::ParticleTemplate::TimeMark::from_other;
      break;
   default:
      throw(RestoreError("wrong Mode value at reading model::ParticleTemplate::TimeMark"));
   }
   p_stream>>p_tm.time;
   p_stream>>p_tm.other;
   return p_stream;
}
QDataStream &operator<<(QDataStream &p_stream,const model::ParticleTemplate::EmitterBlock &p_eb)
{
   return p_stream<<p_eb.name<<p_eb.interval_min<<p_eb.interval_max<<p_eb.time1<<p_eb.time2<<p_eb.emitter;
}
RestoreStream &operator>>(RestoreStream &p_stream,model::ParticleTemplate::EmitterBlock &p_eb)
{
   p_stream>>p_eb.name>>p_eb.interval_min>>p_eb.interval_max>>p_eb.time1>>p_eb.time2;
   p_stream>>p_eb.emitter;
   return p_stream;
}
QDataStream &operator<<(QDataStream &p_stream,const model::ParticleTemplate::FloatControlPoint &p_fcp)
{
   return p_stream<<p_fcp.time<<p_fcp.value;
}
RestoreStream &operator>>(RestoreStream &p_stream,model::ParticleTemplate::FloatControlPoint &p_fcp)
{
   p_stream>>p_fcp.time>>p_fcp.value;
   return p_stream;
}
QDataStream &operator<<(QDataStream &p_stream,const model::ParticleTemplate::ColorControlPoint &p_ccp)
{
   return p_stream<<p_ccp.time<<p_ccp.color;
}
RestoreStream &operator>>(RestoreStream &p_stream,model::ParticleTemplate::ColorControlPoint &p_ccp)
{
   p_stream>>p_ccp.time>>p_ccp.color;
   return p_stream;
}
RestoreStream &operator>>(RestoreStream &p_stream,model::ParticleTemplate *&p_pt)
{
   return Identified::restore_object(p_stream,p_pt);
}
/*bool Identified::is_particle_template() const
{
   return metaObject()->className()==model::ParticleTemplate::staticMetaObject.className();
}*/
